package edu.hawaii.teams.halealohacli.processor;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.WattDepotClient;
import edu.hawaii.teams.halealohacli.command.CurrentPower;
import edu.hawaii.teams.halealohacli.command.DailyEnergy;
import edu.hawaii.teams.halealohacli.command.EnergySince;
import edu.hawaii.teams.halealohacli.command.MonitorGoal;
import edu.hawaii.teams.halealohacli.command.MonitorPower;
import edu.hawaii.teams.halealohacli.command.RankTowers;
import edu.hawaii.teams.halealohacli.command.SetBaseLine;

/**
 * Handles the parsing of user input.
 * 
 * @author Jason Yeo
 * @author Jayson T. Gamiao
 * @author Hansen Cheng
 * @author Rylan Kwock
 * @author Marifel Barbasa
 * 
 */
public class Processor {
  // Calendar object for timestamps.
  private Calendar rightNow;

  // Double for data obtained from server.
  private Double dataOut;

  private SimpleDateFormat dateFormat;

  // String to display data from server.
  private String dataDump;
  private String monitorGoalOutput;
  // Container arrays for each command, used to split the arguments
  private String[] currentPower;
  private String[] dailyEnergy;
  private String[] energySince;
  private String[] rankTowers;
  private String[] monitorPower;
  private String[] setBaseLine;
  private String[] monitorGoal;

  private String[] splitValues;
  private String towerName;
  // To hold the 24 values returned from SetBaseLine class
  private String[] hourValues;

  // Objects for each of the commands
  CurrentPower cp;
  DailyEnergy de;
  EnergySince es;
  RankTowers rt;
  SetBaseLine sb;
  MonitorPower mp;
  MonitorGoal mg;

  // Copy of the WattDepotClient object in the Main.java, used to pass to the respective commands.
  WattDepotClient processorClient;

  // Constant double used to convert from watt to kilowatt.
  static final double KILO = 1000.0;
  // String to split commands
  static final String split = "\\s+";

  /**
   * Constructor for the Processor Class. Initializes all the local variables for future use
   * 
   * @param client a WattDepotClient object from Main.java. Used for passing to the respective
   * commands
   */
  public Processor(WattDepotClient client) {
    this.rightNow = Calendar.getInstance();
    this.dataOut = 0.0;
    this.dateFormat = new SimpleDateFormat("MM", Locale.US);
    this.dataDump = null;

    currentPower = null;
    dailyEnergy = null;
    energySince = null;
    rankTowers = null;
    setBaseLine = null;
    monitorPower = null;
    monitorGoal = null;

    splitValues = null;
    towerName = null;
    hourValues = new String[24];

    cp = new CurrentPower();
    de = new DailyEnergy();
    es = new EnergySince();
    rt = new RankTowers();
    sb = new SetBaseLine();
    mp = new MonitorPower();
    mg = new MonitorGoal();

    processorClient = client;
  }

  /**
   * Splits the input and calls the getData method in the CurrentPower.java class Format for this
   * command should be current-power [tower | lounge].
   * 
   * @param input User input from the prompt
   * @throws Exception Any errors that occur
   */
  public void parseCurrentPower(String input) throws Exception {
    this.currentPower = input.split(split);
    // Correct number of arguments which should be 1
    if (currentPower.length == 2) {
      this.dataDump = cp.getData(processorClient, currentPower[1]);
      try {
        this.dataOut = Double.parseDouble(this.dataDump);
        System.out.format(currentPower[1] + "'s power as of " + rightNow.get(Calendar.YEAR) + "-"
            + dateFormat.format(rightNow.getTime()) + "-" + rightNow.get(Calendar.DAY_OF_MONTH)
            + " " + rightNow.get(Calendar.HOUR) + ":" + rightNow.get(Calendar.MINUTE) + ":"
            + rightNow.get(Calendar.SECOND) + " was %.1f kW.", this.dataOut);
      }
      catch (NumberFormatException e) {
        System.err.print(e.getMessage());
      }
    }
    else {
      System.out.println("Error: Format for this command should be current-power [tower | lounge]");
    }
  }

  /**
   * Splits the input and validates on the correct number of arguments. Format for this command
   * should be daily-energy [tower | lounge] [date]. Also checks for the data formatting using a
   * RegEx of yyyy-mm-dd
   * 
   * @param input User input from the prompt
   * @throws Exception Any errors that occur
   */
  public void parseDailyEnergy(String input) throws Exception {
    this.dailyEnergy = input.split(split);

    // Correct number of arguments should be 2
    if (dailyEnergy.length == 3) {
      if (isValid(dailyEnergy[2])) {
        this.dataDump = de.getData(processorClient, dailyEnergy[1], dailyEnergy[2]);
        try {
          this.dataOut = Double.parseDouble(this.dataDump);
          System.out.format(dailyEnergy[1] + "'s energy consumption for " + dailyEnergy[2]
              + " was %.0f kWh.", this.dataOut);
        }
        catch (NumberFormatException e) {
          System.err.print(e.getMessage());
        }
      }
    }
    else {
      System.out
          .println("Error: Format for this command should be daily-energy [tower | lounge] [date]");
    }
  }

  /**
   * Splits the input and validates on the correct number of arguments. Format for this command
   * should be energy-since [tower | lounge] [date]. Also checks for the data formatting using a
   * RegEx of yyyy-mm-dd
   * 
   * @param input User input from the prompt
   * @throws Exception Any errors that occur
   */
  public void parseEnergySince(String input) throws Exception {
    this.energySince = input.split(split);

    // Correct number of arguments should be 2
    if (energySince.length == 3) {
      if (isValid(energySince[2])) {
        this.dataDump = es.getData(processorClient, energySince[1], energySince[2]);
        try {
          this.dataOut = Double.parseDouble(this.dataDump);
          System.out.format(
              "Total energy consumption by " + energySince[1] + " from " + energySince[2]
                  + " 00:00:00 to " + rightNow.get(Calendar.YEAR) + "-"
                  + dateFormat.format(rightNow.getTime()) + "-"
                  + rightNow.get(Calendar.DAY_OF_MONTH) + " " + rightNow.get(Calendar.HOUR) + ":"
                  + rightNow.get(Calendar.MINUTE) + ":" + rightNow.get(Calendar.SECOND)
                  + " is: %.1f kWh.", this.dataOut);
        }
        catch (NumberFormatException e) {
          System.err.print(e.getMessage());
        }
      }
    }
    else {
      System.out
          .println("Error: Format for this command should be energy-since [tower | lounge] [date]");
    }
  }

  /**
   * Splits the input and validates on the correct number of arguments. Format for this command
   * should be rank-towers [start] [end]. Also checks for the data formatting using a RegEx of
   * yyyy-mm-dd on both arguments
   * 
   * @param input User input from the prompt
   * @throws Exception Any errors that occur
   */
  public void parseRankTowers(String input) throws Exception {
    String[] towerArray;
    this.rankTowers = input.split(split);
    // Correct number of arguments should be 2
    if (rankTowers.length == 3) {
      if (isValid(rankTowers[1]) && isValid(rankTowers[2])) {
        towerArray = rt.getData(processorClient, rankTowers[1], rankTowers[2]).split("\n");
        System.out.println("For the interval " + rankTowers[1] + " to " + rankTowers[2]
            + ", energy consumption by tower was:");
        for (int i = 0; i < towerArray.length; i++) {
          System.out.println(towerArray[i].substring(0, towerArray[i].length() - 1) + " kWh");
        }
      }
    }
    else {
      System.out.println("Error: Format for this command should be rank-towers [start] [end]");
    }
  }

  /**
   * Sets the base power of the tower base on a particular date. If not date is provided then
   * default is yesterday.
   * 
   * @param input - User input from the command prompt.
   * @throws Exception - error.
   */
  public void parseSetBaseLine(String input) throws Exception {

    // Amount to subtract to get the date of yesterday
    final long TIME = 1000L * 60 * 60 * 24;
    Calendar date = Calendar.getInstance(Locale.US);
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    String yesterday = df.format(date.getTimeInMillis() - TIME);

    String splitter = " ";
    this.setBaseLine = input.split(split);

    if (this.setBaseLine.length == 2) {
      this.dataDump = sb.getData(processorClient, this.setBaseLine[1], yesterday);
      System.out.print("Base line set for " + this.setBaseLine[1] + " for date of " + yesterday);
    }
    else if (this.setBaseLine.length == 3) {
      this.dataDump = sb.getData(processorClient, this.setBaseLine[1], this.setBaseLine[2]);
      System.out.print("Base line set for " + this.setBaseLine[1] + " for date of "
          + this.setBaseLine[2]);
    }
    else {
      System.out.println("Error: Format for this command should be set-base "
          + "[tower | lounge] [date] (optional)");
    }

    // Ensure there is data return
    if (this.dataDump != null) {
      // Split the up the 24 values by the space
      splitValues = this.dataDump.split(splitter);

      towerName = this.setBaseLine[1];
      // Loading the 24 values to an array so the monitor-goal command
      // can retrieve data from this array
      for (int i = 0; i < 24; i++) {
        hourValues[i] = splitValues[i];
      }
    }
  }

  /**
   * Prints timestamp and current power being consumed by source at each interval.
   * 
   * @param input - User input from the command prompt.
   * @throws Exception - error.
   */
  public void parseMonitorPower(String input) throws Exception {
    this.monitorPower = input.split(split);

    // Optional argument is not entered
    if (monitorPower.length == 2) {
      // Create XMLGregorianCalendar to retrieve the current time and date.
      XMLGregorianCalendar timeNow =
          processorClient.getLatestSensorData(this.monitorPower[1]).getTimestamp();

      System.out.println("Monitoring the power consumption of source " + this.monitorPower[1]
          + " at intervals of " + 10 + " seconds,");
      System.out.println("given the following timestamp: " + timeNow);

      while (System.in.available() == 0) {
        this.dataDump = mp.getData(processorClient, this.monitorPower[1], "10");
        System.out.print(this.dataDump);
      }

    }
    else if (monitorPower.length == 3) { // Optional argument is entered
      try {
        // Make sure that the given interval is actually an integer
        int interval = Integer.parseInt(this.monitorPower[2]);

        // Create XMLGregorianCalendar to retrieve the current time and date.
        XMLGregorianCalendar timeNow = 
            processorClient.getLatestSensorData(this.monitorPower[1]).getTimestamp();
        
        System.out.println("Monitoring the power consumption of source " + this.monitorPower[1]
            + " at intervals of " + interval + " seconds,");
        System.out.println("given the following timestamp: " + timeNow);

        while (System.in.available() == 0) {
          this.dataDump = mp.getData(processorClient, this.monitorPower[1], this.monitorPower[2]);
          System.out.print(this.dataDump);
        }
      }
      catch (NumberFormatException e) {
        System.out.println("Error: Both the goal and interval must be integers.");
      }
    }
    else {
      System.out.println("Error: Format for this command should be "
          + "monitor-power [tower | lounge] [interval]");
    }
  }

  /**
   * Splits the input and validates on the correct number of arguments. Format for this command
   * should be monitor-goal [tower | lounge] [goal] [interval]. Also checks for the data formatting
   * using a RegEx of yyyy-mm-dd
   * 
   * @param input User input from the prompt
   * @throws Exception Any errors that occur
   */
  public void parseMonitorGoal(String input) throws Exception {
    this.monitorGoal = input.split(split);
    this.monitorGoalOutput = "Everything is fine.";
    
    // Correct number of arguments should be 3
    if (this.monitorGoal.length == 4) {
      String source = this.monitorGoal[1];
      try {
        // if set-baseline is NOT called, return an error
        if (!source.equals(this.towerName)) {
          this.monitorGoalOutput = "Error: The set-baseline command must first be " +
              "called on the specified source.";
          System.out.println(this.monitorGoalOutput);
          return;
        }
        
        // Make sure that the given goal and interval are actually integers
        int goal = Integer.parseInt(this.monitorGoal[2]);
        int interval = Integer.parseInt(this.monitorGoal[3]);
        
        if (goal < 1 || goal > 99 || interval <= 0) {
          this.monitorGoalOutput = "Error: The goal must be between 1 and 99 (inclusive), " +
              "and the interval must be greater than 0.";
          System.out.println(this.monitorGoalOutput);
          return;
        }
        
        // Create XMLGregorianCalendar to retrieve the current time and date.
        XMLGregorianCalendar timeNow = 
            processorClient.getLatestSensorData(source).getTimestamp();
        
        System.out.println("Monitoring the power consumption of source " + source
            + " at intervals of " + interval + " seconds,");
        System.out.println("given the following timestamp: " + timeNow);
        
        // Run the loop until a key is pressed
        while (System.in.available() == 0) {
          this.dataDump = mg.getData(processorClient, this.monitorGoal[1],
              this.monitorGoal[2], this.monitorGoal[3], this.hourValues[0], this.hourValues[1],
              this.hourValues[2], this.hourValues[3], this.hourValues[4], this.hourValues[5],
              this.hourValues[6], this.hourValues[7], this.hourValues[8], this.hourValues[9],
              this.hourValues[10], this.hourValues[11], this.hourValues[12], this.hourValues[13],
              this.hourValues[14], this.hourValues[15], this.hourValues[16], this.hourValues[17],
              this.hourValues[18], this.hourValues[19], this.hourValues[20], this.hourValues[21],
              this.hourValues[22], this.hourValues[23]);
          System.out.print(this.dataDump);
        }
      }
      catch (NumberFormatException e) {
        this.monitorGoalOutput = "Error: Both the goal and interval must be integers.";
        System.out.println(this.monitorGoalOutput);
      }
    }
    else {
      this.monitorGoalOutput = "Error: Format for this command should be monitor-goal " +
          "[tower | lounge] [goal] [interval]";
      System.out.println(this.monitorGoalOutput);
    }
  }
  
  /**
   * Returns the output from calling parseMonitorGoal.
   * Used for the TestMonitorGoal class.
   * 
   * @return the output from calling parseMonitorGoal
   */
  public String getMonitorGoalOutput() {
    return this.monitorGoalOutput;
  }

  /**
   * Validate format and value.
   * 
   * @param text the date used in the format of yyyy-mm-dd
   * @return boolean on whether the format is valid
   */
  public boolean isValid(String text) {
    if (text == null || !text.matches("\\d{4}-[01]\\d-[0-3]\\d")) {
      return false;
    }
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    df.setLenient(false);
    try {
      df.parse(text);
      return true;
    }
    catch (ParseException ex) {
      return false;
    }
  }
}